﻿using System;

namespace SpatialHashLib
{
    public static class DoubleExtensions
    {
        const int ZeroIntervalExponent = -50;

        public const double GeneralEpsilon = 1.0e-12;
        public const int DoubleExponentBias = 1023;


        public static bool EpsilonEquals(this double self, double other, double epsilon)
        {
            if (double.IsNaN(self))
                return double.IsNaN(other);

            if (double.IsPositiveInfinity(self))
                return double.IsPositiveInfinity(other);

            if (double.IsNegativeInfinity(self))
                return double.IsNegativeInfinity(other);

            if (self == 0)
                return other == 0;

            return Math.Abs(self - other) <= Math.Abs(self * epsilon);
        }

        public static bool EpsilonEquals(this double self, double other)
        {
            return EpsilonEquals(self, other, GeneralEpsilon);
        }

        public static bool Valid(this double value)
        {
            return !(double.IsNaN(value) || double.IsInfinity(value));
        }

        public static bool Invalid(this double value)
        {
            return double.IsNaN(value) || double.IsInfinity(value);
        }

        public static bool Positive(this double self)
        {
            return self > 0.0;
        }

        public static bool IsZero(this double self, double epsilon)
        {
            return -epsilon < self && self < epsilon;
        }

        public static bool IsZero(this double self)
        {
            return -GeneralEpsilon < self && self < GeneralEpsilon;
        }

        public static long ToBits(this double self)
        {
            return BitConverter.ToInt64(BitConverter.GetBytes(self), 0);
        }

        // Assumes IEEE-754 standard for double: from MSbit to LSbit:
        //      1 sign
        //      11 exponent 
        //      57 mantissa
        //  value = sign * mantissa * 2^exponent
        public static int BiasedExponent(this double self)
        {
            // strip of the mantissa and sign to get the biased exponent
            return
                (int)(self.ToBits() >> 52) & 0x07FF;
        }

        public static int UnBiasedExponent(this double self)
        {
            return self.BiasedExponent() - DoubleExponentBias;
        }

        public static double FromBits(this long self)
        {
            return BitConverter.ToDouble(BitConverter.GetBytes(self), 0);
        }

        // returns 2 raised to the specified power, where -1022 < power < 1023
        public static double Base2Power(this int self)
        {
            if (self > 1023 || self < -1022)
                throw new ArgumentException("Exponent " + self + " is out of bounds (must be greater than -1022 and less than 1023).");

            long expBias = self + DoubleExponentBias;
            return FromBits((long)(expBias << 52));
        }

        public static bool IsZeroInterval(this double self, double other, double epsilon)
        {
            double width = Math.Abs(self - other);
            if (width < epsilon)
                return true;

            double scaledWidth = width / Math.Max(Math.Abs(self), Math.Abs(other));

            return scaledWidth.UnBiasedExponent() <= ZeroIntervalExponent;
        }

        public static bool IsZeroInterval(this double self, double other)
        {
            return IsZeroInterval(self, other, GeneralEpsilon);
        }
    }
}
